home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.1 (Developer) [x86]
/
NeXT Step 3.1 Intel dev.cdr.dmg
/
NextDeveloper
/
Examples
/
AppKit
/
Draw
/
Image.m
< prev
next >
Wrap
Text File
|
1993-01-06
|
17KB
|
654 lines
#import "draw.h"
/* Optimally viewed in a wide window. Make your window big enough so that this comment fits on one line without wrapping. */
/*
* Image is a simple graphic which takes PostScript or
* TIFF images and draws them in a bounding box (it scales
* the image if the bounding box is changed). It is
* implemented using the NXImage class. Using NXImage
* here is especially nice since it images its PostScript
* in a separate context (thus, any errors that PostScript
* generates will not affect our main drawing context).
*/
@implementation Image : Graphic
/* Initialize the class */
+ initialize
{
[Image setVersion:7];
return self;
}
/* Factory methods. */
+ highlightedLinkButtonImage:(NXSize *)size
/*
* Just makes an NXLinkButtonH NXImage the same size as
* the size passed in. I suppose this could just be a
* function.
*/
{
static NXImage *retval = nil;
if (!retval) {
retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
[retval setScalable:YES];
[retval setDataRetained:YES];
}
[retval setSize:size];
return retval;
}
+ (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
{
return [NXImage canInitFromPasteboard:pboard];
}
static BOOL checkImage(NXImage *anImage)
/*
* Locking focus on an NXImage forces it to draw and thus verifies
* whether there are any PostScript or TIFF errors in the source of
* the image. lockFocus returns YES only if there are no errors.
*/
{
if ([anImage lockFocus]) {
[anImage unlockFocus];
return YES;
}
return NO;
}
/* Creation/Initialization Methods */
- init
/*
* This creates basically an "empty" Image.
* This is the designated initializer for Image.
* Be careful, however, because by the time this
* returns, a newly initialized Image may not be
* fully initialized (it'll be "valid," just not
* necessarily fully initialized). If you want that
* behaviour, override finishedWithInit.
*/
{
[super init];
originalSize.width = originalSize.height = 1.0;
bounds.size = originalSize;
return self;
}
- finishedWithInit
/*
* Called when a newly initialized Image is fully
* initialized and ready to roll. For subclassers
* only.
*/
{
return self;
}
- initEmpty
/*
* Creates a blank Image.
*/
{
[self init];
return [self finishedWithInit];
}
- initFromStream:(NXStream *)stream
/*
* Creates a new NXImage and sets it to be scalable and to retain
* its data (which means that when we archive it, it will actually
* write the TIFF or PostScript data into the stream).
*/
{
[self init];
if (stream) {
image = [NXImage allocFromZone:[self zone]];
if ((image = [image initFromStream:stream])) {
[image setDataRetained:YES];
if (checkImage(image)) {
[image getSize:&originalSize];
[image setScalable:YES];
bounds.size = originalSize;
return [self finishedWithInit];
}
}
}
[self free];
return nil;
}
- initFromPasteboard:(Pasteboard *)pboard;
/*
* Creates a new NXImage and sets it to be scalable and to retain
* its data (which means that when we archive it, it will actually
* write the TIFF or PostScript data into the stream).
*/
{
[self init];
if (pboard) {
image = [NXImage allocFromZone:[self zone]];
if ((image = [image initFromPasteboard:pboard])) {
[image setDataRetained:YES];
if (checkImage(image)) {
[image getSize:&originalSize];
[image setScalable:YES];
bounds.size = originalSize;
return [self finishedWithInit];
}
}
}
[self free];
return nil;
}
- initFromFile:(const char *)file
/*
* Creates an NXImage by reading data from an .eps or .tiff file.
*/
{
[self init];
image = [[NXImage allocFromZone:[self zone]] init];
if ([image loadFromFile:file]) {
[image setDataRetained:YES];
if (checkImage(image)) {
[image getSize:&originalSize];
[image setScalable:YES];
bounds.size = originalSize;
return [self finishedWithInit];
}
}
[self free];
return nil;
}
- doInitFromImage:(NXImage *)anImage
/*
* Common code for initFromImage: and unarchiving.
*/
{
if (anImage) {
image = anImage;
[image getSize:&originalSize];
[image setScalable:YES];
[image setDataRetained:YES];
bounds.size = originalSize;
} else {
[self free];
self = nil;
}
return self;
}
- initFromImage:(NXImage *)anImage
/*
* Initializes an Image from a specific NXImage.
*/
{
[self init];
return [[self doInitFromImage:anImage] finishedWithInit];
}
- initFromIcon:(NXImage *)anImage
/*
* Same as initFromImage:, but we remember that this particular
* NXImage was actually a file icon (which enables us to double-click
* on it to open the icon, see handleEvent:).
*/
{
if ([self initFromImage:anImage]) {
amIcon = YES;
return self;
} else {
return nil;
}
}
- initWithLinkButton
/*
* Creates an image which is just the link button.
* This is only applicable with Object Links.
*/
{
if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
amLinkButton = YES;
return self;
} else {
return nil;
}
}
- (NXRect)resetImage:(NXImage *)newImage
/*
* Called by the "reinit" methods to reset all of our instance
* variables based on using a new NXImage for our image.
*/
{
NXRect eBounds, neBounds;
[image free];
image = newImage;
[self getExtendedBounds:&eBounds];
[image getSize:&neBounds.size];
neBounds.size.width *= bounds.size.width / originalSize.width;
neBounds.size.height *= bounds.size.height / originalSize.height;
neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
[self setBounds:&neBounds];
[self getExtendedBounds:&neBounds];
NXUnionRect(&eBounds, &neBounds);
[image setDataRetained:YES];
[image getSize:&originalSize];
[image setScalable:YES];
return neBounds;
}
- (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
/*
* Reset all of our instance variable based on extract an
* NXImage from data in the the passed pboard. Happens when
* we update a link through Object Links.
*/
{
NXRect neBounds;
NXImage *newImage;
newImage = [NXImage allocFromZone:[self zone]];
if ((newImage = [newImage initFromPasteboard:pboard])) {
[newImage setDataRetained:YES];
if (checkImage(newImage)) return [self resetImage:newImage];
}
[newImage free];
neBounds.origin.x = neBounds.origin.y = 0.0;
neBounds.size.width = neBounds.size.height = 0.0;
return neBounds;
}
- (NXRect)reinitFromFile:(const char *)file
/*
* Reset all of our instance variable based on extract an
* NXImage from the data in the passed file. Happens when
* we update a link through Object Links.
*/
{
NXRect neBounds;
NXImage *newImage;
newImage = [[NXImage allocFromZone:[self zone]] init];
if ([newImage loadFromFile:file]) {
[newImage setDataRetained:YES];
if (checkImage(newImage)) return [self resetImage:newImage];
}
[newImage free];
neBounds.origin.x = neBounds.origin.y = 0.0;
neBounds.size.width = neBounds.size.height = 0.0;
return neBounds;
}
/* All those allocation/initialization method and only this one free method. */
- free
{
[image free];
return [super free];
}
/* Link methods */
- setLink:(NXDataLink *)aLink
/*
* It's "might" be linked because we're linked now, but might
* have our link broken in the future and the mightBeLinked flag
* is only advisory and is never cleared. It is used just so that
* we know we might want to try to reestablish a link with this
* Graphic after a cut/paste. No biggie if there really is no
* link associated with this any more. In gvLinks.m, see
* readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
* gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
* If this Image is a link button, then we obviously never need
* to update the link because we don't actually show the data
* associated with the link (we just show that little link button).
*/
{
NXDataLink *oldLink = link;
link = aLink;
gFlags.mightBeLinked = YES;
if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
return oldLink;
}
- (NXDataLink *)link
{
return link;
}
/* Event-handling */
- trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
/*
* This method tracks that little link button. Note that the link button is a diamond,
* but we track the whole rectangle. This is unfortunate, but we can't be sure that,
* in the future, the shape of the link button might not change (thus, what we really
* need is a NeXTSTEP function to track the thing!). Anyway, we track it and if the
* mouse goes up inside the button, we openSource on the link (we wouldn't be here if
* we didn't have a link).
*/
{
NXPoint p;
NXImage *realImage, *highImage, *imageToDraw;
p = *startPoint;
realImage = image;
highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
image = imageToDraw = highImage;
[self draw];
[[view window] flushWindow];
do {
event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
p = event->location;
[view convertPoint:&p fromView:nil];
imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
if (imageToDraw != image) {
image = imageToDraw;
[self draw];
[[view window] flushWindow];
}
} while (event->type != NX_MOUSEUP);
if (imageToDraw == highImage) {
[link openSource];
image = realImage;
[self draw];
[[view window] flushWindow];
}
return self;
}
- (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
{
if (NXMouseInRect(p, &bounds, NO)) {
if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
[self trackLinkButton:event at:p inView:view];
return YES;
} else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
[NXApp getNextEvent:NX_MOUSEUPMASK];
[link openSource];
return YES;
}
}
return NO;
}
/* Methods overridden from superclass to support links. */
- (int)cornerMask
/*
* Link buttons are too small to have corners AND sides, so
* we only let link buttons have knobbies on the corners.
*/
{
if (amLinkButton) {
return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
} else {
return [super cornerMask];
}
}
- (NXRect *)getExtendedBounds:(NXRect *)theRect
/*
* We have to augment this because we might have a link frame
* (if show links is on), so we have to extend our extended bounds
* a bit.
*/
{
NXRect linkBounds, *retval;
float linkFrameThickness = NXLinkFrameThickness();
linkBounds = bounds;
linkBounds.origin.x -= linkFrameThickness;
linkBounds.size.width += linkFrameThickness * 2.0;
linkBounds.origin.y -= linkFrameThickness;
linkBounds.size.height += linkFrameThickness;
retval = [super getExtendedBounds:theRect];
return NXUnionRect(&linkBounds, retval);
}
- (BOOL)constrainByDefault;
/*
* Icons and link buttons look funny outside their natural
* aspect ratio, so we constrain them (by default) to keep
* their natural ratio. You can still use the Alternate key
* to NOT constrain these.
*/
{
return (amLinkButton || amIcon);
}
/* Methods overridden from superclass */
- (BOOL)isValid
{
return image ? YES : NO;
}
- (BOOL)isOpaque
{
return [[image bestRepresentation] isOpaque];
}
- (float)naturalAspectRatio
{
if (!originalSize.height) return 0.0;
return originalSize.width / originalSize.height;
}
- draw
/*
* If we are resizing, we just draw a gray box.
* If not, then we simply see if our bounds have changed
* and update the NXImage object if they have. Then,
* if we do not allow alpha (i.e. this is a TIFF image),
* we paint a white background square (we don't allow
* alpha in our TIFF images since it won't print and
* Draw is WYSIWYG). Finally, we SOVER the image.
* If we are not keeping the cache around, we tell
* NXImage to toss its cached version of the image
* via the message recache.
*
* If we are linked to something and the user has chosen
* "Show Links", then linkOutlinesAreVisible, so we must
* draw a link border around ourself.
*/
{
NXRect r;
NXPoint p;
NXSize currentSize;
if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
if (DrawStatus == Resizing) {
PSsetgray(NX_DKGRAY);
PSsetlinewidth(0.0);
PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
} else if (image) {
p = bounds.origin;
[image getSize:¤tSize];
if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
if ([image isScalable]) {
[image setSize:&bounds.size];
} else {
p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
}
}
if ([[image bestRepresentation] isOpaque]) {
PSsetgray(NX_WHITE);
NXRectFill(&bounds);
}
[image composite:NX_SOVER toPoint:&p];
if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
r.origin.x = floor(bounds.origin.x);
r.origin.y = floor(bounds.origin.y);
r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
NXFrameLinkRect(&r, YES); // YES means "is a destination link"
}
}
return self;
}
/* Direct writing of EPS or TIFF. */
- (BOOL)canEmitEPS
/*
* If we have a representation that can provide EPS directly, then,
* if we are copying PostScript to the Pasteboard and this Image is the
* only Graphic selected, then we might as well just have the EPS which
* represents this Image go straight to the Pasteboard rather than
* wrapping it up in the copyPSCodeInside: wrappers. Of course, we
* can only do that if we haven't been resized.
*
* See gvPasteboard.m's writePSToStream:.
*/
{
List *reps = [image representationList];
int i = [reps count];
if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
while (i--) {
if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
return YES;
}
}
}
return NO;
}
- writeEPSToStream:(NXStream *)stream
/*
* If canEmitEPS above returns YES, then we can write ourself out directly
* as EPS. This method does that.
*/
{
List *reps = [image representationList];
int i = [reps count];
char *data;
int length;
while (i--) {
if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
[[reps objectAt:i] getEPS:&data length:&length];
NXWrite(stream, data, length);
return self; // should I free data before returning?
}
}
return self;
}
- (BOOL)canEmitTIFF
/*
* Similar to canEmitEPS, except its for TIFF.
*/
{
return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
}
- writeTIFFToStream:(NXStream *)stream
/*
* Ditto above.
*/
{
[image writeTIFF:stream allRepresentations:YES];
return self;
}
/* Caching. */
- setCacheable:(BOOL)flag
{
dontCache = flag ? NO : YES;
return self;
}
- (BOOL)isCacheable
{
return !dontCache;
}
/* Archiving. */
- write:(NXTypedStream *)stream
/*
* All that is needed to archive the NXImage.
*/
{
[super write:stream];
NXWriteType(stream, "c", &amLinkButton);
NXWriteType(stream, "c", &amIcon);
if (!amLinkButton) {
NXWriteObject(stream, image);
NXWriteSize(stream, &originalSize);
}
return self;
}
- read:(NXTypedStream *)stream
/*
* This contains lots of compatibility code for
* interim versions. See if you can figure out the
* various ways we approached archiving link info!
*/
{
BOOL alphaOk;
NXRect savedBounds;
int version, linkNumber;
[super read:stream];
version = NXTypedStreamClassVersion(stream, "Image");
if (version > 5) NXReadType(stream, "c", &amLinkButton);
if (version > 6) NXReadType(stream, "c", &amIcon);
if (amLinkButton) {
savedBounds = bounds;
[self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
bounds = savedBounds;
} else {
image = NXReadObject(stream);
NXReadSize(stream, &originalSize);
}
if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
if (version == 4) {
NXReadObject(stream); // used to be the NXDataLink
} else if (version > 2 && version < 6) {
NXReadTypes(stream, "i", &linkNumber);
}
return self;
}
@end